/*
 [The "BSD license"]
 Copyright (c) 2011-2011 Joel Li (李家智)
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
 3. The name of the author may not be used to endorse or promote products
     derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bee.tl.core;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import org.bee.tl.ext.DateFormat;
import org.bee.tl.ext.DateFunction;
import org.bee.tl.ext.DebugFunction;
import org.bee.tl.ext.DeleteTag;
import org.bee.tl.ext.IncludeFileTemplateTag;
import org.bee.tl.ext.LayoutTag;
import org.bee.tl.ext.NVLFunction;
import org.bee.tl.ext.Print;
import org.bee.tl.ext.Println;

/**
 * 系统的主要类，通过此类可以做到
 * <p>
 * 获取字符串模板或者文件模板
 * <p>
 * 设置模板属性，如占位符号，严格MVC等
 * <p>
 * 注册自定义方法，标签，格式化函数，虚拟属性，设置自定义错误处理函数
 * <p>
 * 预编译class的存放路径，以及缓存检测时间
 * 
 * @author javamonkey
 * 
 */
public class GroupTemplate {

	String placeholderStart = "${";
	String placeholderEnd = "}";
	String statementStart = "<%";
	String statementEnd = "%>";
	File root = null;
	String rootAlias;

	public final static int EXCEPTION = 0;
	public final static int SHOW_NULL = 1;

	boolean isStrict = false;

	Map<String, Function> fnMap = new HashMap<String, Function>();
	Map<String, Class> textProcessMap = new HashMap<String, Class>();
	Map<String, Format> formatMap = new HashMap<String, Format>();

	List<VirtualAttributeEval> virtualAttributeList = new ArrayList<VirtualAttributeEval>();
	static Logger logger = Logger.getLogger(GroupTemplate.class.toString());
	public String tempFolder = null;

	CachedRuntimeTemplate cachedRuntime = null;
	CompiledClassMap classMap = null;
	ThreadPoolExecutor futureComplileExecutor = null;

	// 允许编译成class 以提高运行效率
	private boolean optimize = false;
	// 开发用，用于输出更多信息，以及忽略cache
	private boolean debug = false;
	boolean nativeCall = false;
	boolean checkTemplateUpdate = true;
	boolean directByteOutput = false;
	// 每隔5秒检测一次
	int checkTemplatePeriod = 5;
	URLClassLoader classLoader;

	String targetClassPath;
	String targetSrcPath;
	String extraClassPath;
	String charset = "GBK";
	// boolean useByte = false ; /*对模板离得文本以二进制方式存储和输出*/
	ErrorHandler errorHandler = new DefaultErrorHandler();
	Thread thread = null;

	protected Map<String, Object> optimizeConfigMap = new HashMap<String, Object>();
	public static String OPTIMIZE_KEEP_SOURCE = "OPTIMIZE_KEEP_SOURCE";
	public static String OPTIMIZE_COMPILE_LATTER = "OPTIMIZE_COMPILE_LATTER";
	public static String OPTIMIZE_COMPILE_WORKER_NUM = "OPTIMIZE_COMPILE_WORKER_NUM";

	/**
	 * 得到错误处理类
	 * 
	 * @return
	 */
	public ErrorHandler getErrorHandler() {
		return errorHandler;
	}

	/**
	 * 设置错误处理类
	 * 
	 * @param errorHandler
	 */
	public void setErrorHandler(ErrorHandler errorHandler) {
		this.errorHandler = errorHandler;
	}

	public GroupTemplate() {

		init();
	}

	/**
	 * 初始化GroupTemplate，模板文件位于root目录
	 * 
	 * @param root
	 */
	public GroupTemplate(File root) {
		this();
		this.root = root;

		this.rootAlias = root.getName();

	}

	/**
	 * 初始化GroupTemplate，模板文件位于root目录，同时，预编译的时候，存放在alias所指示的别名目录下
	 * 
	 * @param root
	 * @param alias
	 */
	public GroupTemplate(File root, String alias) {
		this();
		this.root = root;

		this.rootAlias = alias;
	}

	/**
	 * 得到一个模板，输出是字符串
	 * 
	 * @param input
	 * @return
	 */
	public Template getStringTemplate(String input) {

		BeeTemplate template = new BeeTemplate(input);
		template.setGroupTemplate(this);
		return template;

	}

	/**
	 * 得到一个模板，输出是一个Reader
	 * 
	 * @param reader
	 * @return
	 * @throws IOException
	 */
	public Template getReaderTemplate(Reader reader) throws IOException {
		BeeTemplate template = new BeeTemplate(reader);
		template.setGroupTemplate(this);
		return template;
	}

	/**
	 * 得到一个文件模板
	 * 
	 * @param child
	 *            模板文件相对于root目录的位置，譬如/error/404.html
	 * @return
	 * @throws IOException
	 */
	public Template getFileTemplate(String child) throws IOException {
		if (this.optimize) {
			CompiledBeeTemplate template = new CompiledBeeTemplate(child, this);

			return template;

		} else {

			File file = new File(this.root, child);
			BeeTemplate template = this.cachedRuntime.getBeeTemplate(file);
			if (template == null) {

				template = new BeeTemplate(file, this.charset);
				template.setGroupTemplate(this);
				template.parse();
				this.cachedRuntime.updateCache(template, file);
				return template;
			}

			BeeTemplate newTemplate = template.copy();

			return newTemplate;
		}
	}

	private void init() {

		this.cachedRuntime = new CachedRuntimeTemplate(this);
		// function
		this.registerFunction("date", new DateFunction());
		this.registerFunction("nvl", new NVLFunction());
		this.registerFunction("debug", new DebugFunction());

		this.registerFunction("print", new Print());
		this.registerFunction("println", new Println());

		// format
		this.registerFormat("dateFormat", new DateFormat());
		// tag
		this.registerTag("deleteTag", DeleteTag.class);
		this.registerTag("includeFileTemplate", IncludeFileTemplateTag.class);
		this.registerTag("layout", LayoutTag.class);

		// virtual attribute
		this.registerVirtualAttributeEval(new VirtualAttributeEval() {
			public Integer eval(Object o, String attributeName, Context ctx) {
				if (attributeName.equals("size")) {
					if (o instanceof Collection) {
						return ((Collection) o).size();
					} else if (o instanceof Map) {
						return ((Map) o).size();
					} else if (o.getClass().isArray()) {
						return ((Object[]) o).length;

					} else {
						throw new IllegalArgumentException();
					}

				} else {
					throw new IllegalArgumentException();
				}

			}

			public boolean isSuppoert(Class c, String attributeName) {
				if ((Map.class.isAssignableFrom(c)
						|| Collection.class.isAssignableFrom(c) || c.isArray())
						&& attributeName.equals("size")) {
					return true;
				} else {
					return false;
				}
			}
		});

	}

	/**
	 * 配置模板
	 * 
	 * @param statementStart
	 * @param statementEnd
	 * @param holderStart
	 * @param holderEnd
	 */
	public void config(String statementStart, String statementEnd,
			String holderStart, String holderEnd) {
		this.setStatementStart(statementStart);
		this.setStatementEnd(statementEnd);
		this.setPlaceholderStart(holderStart);
		this.setPlaceholderEnd(holderEnd);
	}

	/**
	 * @param statementStart
	 * @param statementEnd
	 * @param holderStart
	 * @param holderEnd
	 * @param optimize
	 *            是否优化，即运行时刻 编译成class
	 */
	public void config(String statementStart, String statementEnd,
			String holderStart, String holderEnd, boolean optimize) {
		this.setStatementStart(statementStart);
		this.setStatementEnd(statementEnd);
		this.setPlaceholderStart(holderStart);
		this.setPlaceholderEnd(holderEnd);
		if (optimize)
			this.enableOptimize();
	}

	/**
	 * @param statementStart
	 * @param statementEnd
	 * @param holderStart
	 * @param holderEnd
	 * @param optimize
	 *            是否优化，即运行时刻 编译成class
	 * @param nativeCall
	 *            是否运行模板里直接调用java类属性或者方法
	 */
	public void config(String statementStart, String statementEnd,
			String holderStart, String holderEnd, boolean optimize,
			boolean nativeCall) {
		this.setStatementStart(statementStart);
		this.setStatementEnd(statementEnd);
		this.setPlaceholderStart(holderStart);
		this.setPlaceholderEnd(holderEnd);
		if (optimize)
			this.enableOptimize();
		if (nativeCall)
			this.enableNativeCall();
	}

	/**
	 * 设置严格MVC模式，变量定义，算式表达式，复杂的逻辑表达式，方法调用等都将不被允许。这有助于模板视图分离 下列例子在此严格MVC摸下都将报错
	 * <p>
	 * 
	 * <pre>
	 * &lt;%var x = 1 ;%&gt;
	 * ${x+y}
	 * &lt;%if(x&gt;1){%&gt;
	 * ${callFunction()}
	 * </pre>
	 * 
	 */
	public void enableStrict() {
		// 设置模板语言是否是严格的MVC，即不运行表达式，临时变量等
		this.isStrict = true;
	}

	/**
	 * 允许将模板里的文本以byte方式直接输出，这样会省略字符集编码，提高20%性能以及CPU消耗，此方式是用还必须配合
	 * getText(OutputStream os)是用，否则使用无效 默认不开启
	 * 
	 * 
	 */
	public void enableDirectOutputByte() {
		this.directByteOutput = true;
	}

	/**
	 * 允许本地调用, 可以调用instance的public方法和属性，也可以调用静态类的属性和方法 ,需要加一个@指示此调用是直接调用class
	 * <p>
	 * 
	 * <pre>
	 * 
	 * 
	 * 	$@user.getMaxFriend(“lucy”)$
	 * 	$@user.maxFriend$
	 * 	$@com.xxxx.constants.Order.getMaxNum()$
	 * 	$@com.xxxx.constants.Order.MAX_NUM$
	 * </pre>
	 */
	public void enableNativeCall() {
		this.nativeCall = true;
	}

	/**
	 * 注册一个虚拟属性,虚拟属性为java对象添加额外的显示属性而不需要重写java类
	 * 
	 * @param e
	 */
	public void registerVirtualAttributeEval(VirtualAttributeEval e) {
		virtualAttributeList.add(e);

	}

	/**
	 * 注册一个自定义函数,参考{@link org.bee.tl.ext.DateFunction} 注意，不能定义一个同名标签
	 * 
	 * @param name
	 * @param fn
	 */
	public void registerFunction(String name, Function fn) {
		if (this.containTag(name)) {
			throw new RuntimeException("Function和Tag方法名不能重复:" + name);
		}
		this.fnMap.put(name, fn);
	}

	/**
	 * 注册一个function包，包名由packageName指定，方法名是对象o里的所有方法
	 * 如果方法定义为void，则方法返回null，如果方法参数最后一个Context，则传入一个Context
	 * 
	 * @param packageName
	 * @param o
	 */
	public void registerFunctionPackage(String packageName, Object o) {
		Method[] ms = BeetlUtil.getSelfMethod(o);
		for (Method m : ms) {
			FunctionWrapper fw = new FunctionWrapper(o, m);
			this.registerFunction(packageName + "." + fw.getFunctionName(), fw);
		}

	}

	/**
	 * 注册一个自定义标签，参考{@link org.bee.tl.ext.IncludeFileTemplate} 注意，不能定义一个同名函数
	 * 
	 * @param name
	 * @param tag
	 */
	public void registerTag(String name, Class tag) {
		if (this.containFunction(name)) {
			throw new RuntimeException("Tag和Function方法名不能重复:" + name);
		}
		this.textProcessMap.put(name, tag);
	}

	/**
	 * 注册一个自定义格式化函数，参考{@link org.bee.tl.ext.DateFormat}
	 * 
	 * @param name
	 * @param format
	 */
	public void registerFormat(String name, Format format) {

		this.formatMap.put(name, format);
	}

	protected VirtualAttributeEval getVirtualAttributeEval(Class c,
			String attributeName) {
		for (VirtualAttributeEval eval : virtualAttributeList) {
			if (eval.isSuppoert(c, attributeName)) {
				return eval;
			}
		}
		return null;
	}

	protected Tag getTag(String name) {
		Class process = this.textProcessMap.get(name);
		if (process == null) {
			return null;
		} else {
			try {
				return (Tag) process.newInstance();
			} catch (InstantiationException e) {
				return null;
			} catch (IllegalAccessException e) {
				return null;
			}
		}

	}

	public Function getFunction(String name) {
		Function fn = fnMap.get(name);
		return fn;
	}

	public Format getFormat(String name) {
		Format fn = formatMap.get(name);
		return fn;
	}

	public boolean containFunction(String name) {
		return fnMap.containsKey(name);
	}

	public boolean containTag(String name) {
		return textProcessMap.containsKey(name);
	}

	public boolean containFormat(String name) {
		return formatMap.containsKey(name);
	}

	public String getPlaceholderStart() {
		return placeholderStart;
	}

	public void setPlaceholderStart(String placeholderStart) {
		this.placeholderStart = placeholderStart;
	}

	public String getPlaceholderEnd() {
		return placeholderEnd;
	}

	public void setPlaceholderEnd(String placeholderEnd) {
		this.placeholderEnd = placeholderEnd;
	}

	public String getStatementStart() {
		return statementStart;
	}

	public void setStatementStart(String statementStart) {
		this.statementStart = statementStart;
	}

	public String getStatementEnd() {
		return statementEnd;
	}

	public void setStatementEnd(String statementEnd) {
		this.statementEnd = statementEnd;
	}

	/**
	 * 运行groupTemplate优化模板，即编译成class运行,class位于tempFolder目录下，如果未通过
	 * {@link #setTempFolder(int, int) setTempFolder} 设置，则class存放在用户主目录下的.bee目录下
	 */
	public void enableOptimize() {
		this.optimize = true;
		this.classMap = new CompiledClassMap(this);
		if (tempFolder == null) {

			tempFolder = System.getProperty("BEE_GENERATED_CLASS_PATH");
			if (tempFolder == null) {
				tempFolder = System.getProperty("user.home") + File.separator
						+ ".bee";
			}
		}
		targetClassPath = tempFolder + File.separator + this.getRootAlias()
				+ File.separator + "classes";
		targetSrcPath = targetClassPath;

		if (optimizeConfigMap != null) {
			Object keep = optimizeConfigMap
					.get(GroupTemplate.OPTIMIZE_KEEP_SOURCE);
			if (keep != null && ((Boolean) keep).booleanValue()) {
				// 如果需要保持生成的原有文件,将原程序生成到单独一个目录
				targetSrcPath = tempFolder + File.separator
						+ this.getRootAlias() + File.separator + "src";
			}

		}

		new File(targetClassPath).mkdirs();
		new File(targetSrcPath).mkdirs();
		newLoader();

	}

	public void enableOptimize(Map optimizeConfigMap) {
		this.optimizeConfigMap = optimizeConfigMap;
		Boolean compileAsAnotherThread = (Boolean) optimizeConfigMap
				.get(this.OPTIMIZE_COMPILE_LATTER);
		if (compileAsAnotherThread != null
				&& compileAsAnotherThread.booleanValue()) {
			// 异步编译，避免web启动的时候大量访问阻塞在beetl优化的时候
			Integer numInteger = (Integer) optimizeConfigMap
					.get(this.OPTIMIZE_COMPILE_WORKER_NUM);
			int workerNum = 1;
			if (numInteger != null) {
				workerNum = numInteger.intValue();
			}
			this.futureComplileExecutor = new ThreadPoolExecutor(workerNum,
					workerNum, 100, TimeUnit.SECONDS, new LinkedBlockingQueue());
		}

		enableOptimize();
	}

	protected URLClassLoader getTemplateClassLoader() {
		return classLoader;
	}

	/**
	 * 如果模板文件更新，beetl将重新编译此模板，并通过新的classloader使用新编译模板文件
	 */
	protected synchronized void newLoader() {
		try {
			URL url = new File(targetClassPath).toURI().toURL();
			if (this.extraClassPath != null) {
				String[] urls = this.extraClassPath.split(";");
				URL[] classUrl = new URL[urls.length + 1];
				int i = 0;
				for (String u : urls) {
					classUrl[i++] = new File(u).toURL();
				}
				classUrl[i] = url;
				classLoader = new URLClassLoader(classUrl, this.getClass()
						.getClassLoader());

			} else {
				classLoader = new URLClassLoader(new URL[] { url }, this
						.getClass().getClassLoader());
			}

		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public String getTargetClassPath() {
		return targetClassPath;
	}

	public String getTargetSrcPath() {
		return targetSrcPath;
	}

	public String getRootAlias() {
		return rootAlias;
	}

	public boolean isDebug() {
		return debug;
	}

	/**
	 * 开发使用，用于取消缓存，以及输出更为详细的调试信息
	 * 
	 * @param debug
	 */
	public void setDebug(boolean debug) {
		this.debug = debug;
	}

	public boolean isCheckTemplateUpdate() {
		return checkTemplateUpdate;
	}

	public int getCheckTemplatePeriod() {
		return checkTemplatePeriod;
	}

	public String getTempFolder() {
		return tempFolder;
	}

	public void setTempFolder(String tempFolder) {
		this.tempFolder = tempFolder;
	}

	public String getExtraClassPath() {
		return extraClassPath;
	}

	/**
	 * 设置预编译功能需要额外的classpath，一般用不到
	 * 
	 * @param extraClassPath
	 */
	public void setExtraClassPath(String extraClassPath) {
		this.extraClassPath = extraClassPath;
	}

	/**
	 * 检查模板文件是否变动，单位是秒，在开发模式下，设置1秒，2秒都可以，在产品模式下，设置为0，将不检查模板更新
	 * 
	 * @param period
	 */
	public void enableChecker(final int period) {
		if (thread != null)
			return;
		thread = new Thread(new Runnable() {

			public void run() {
				while (true) {

					try {
						if (GroupTemplate.this.cachedRuntime != null)
							GroupTemplate.this.cachedRuntime.checkTemplateUpdate();
						if (GroupTemplate.this.classMap != null)
							GroupTemplate.this.classMap.checkTemplateUpdate();
						Thread.sleep(1000 * period);
					} catch (InterruptedException e) {

						return;
					}
				}

			}

		}, "Beetl-Scanner");
		thread.setDaemon(true);
		thread.start();
	}

	public String getCharset() {
		return charset;
	}

	/**
	 * 如果使用模板文件，此方法设置文件的编码格式
	 * 
	 * @param charSet
	 */
	public void setCharset(String charSet) {
		this.charset = charSet;
	}

	/**
	 * 关闭GroupTemplate，清除一些使用资源
	 */
	public void close() {
		if (thread != null)
			thread.interrupt();
		thread = null;
	}

	public Map<String, Object> getOptimizeConfigMap() {
		return optimizeConfigMap;
	}

	public boolean isDirectByteOutput() {
		return directByteOutput;
	}

}
